/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.example.android.apis.graphics

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.opengl.GLSurfaceView
import android.opengl.GLU
import android.opengl.GLUtils
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.example.android.apis.R
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.CharBuffer
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL
import javax.microedition.khronos.opengles.GL10
import javax.microedition.khronos.opengles.GL11
import javax.microedition.khronos.opengles.GL11ExtensionPack
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt

/**
 * Demonstrate how to use the OES_texture_cube_map extension, available on some
 * high-end OpenGL ES 1.x GPUs. Shows how to load and use GL_TEXTURE_CUBE_MAP
 * textures to animate a gyrating Torus.
 */
class CubeMapActivity : AppCompatActivity() {
    /**
     * [GLSurfaceView] we use as our content view, allocated, configured to use our class
     * [Renderer] as its [GLSurfaceView.Renderer], and then set as our content view
     * in our [onCreate] method.
     */
    private var mGLSurfaceView: GLSurfaceView? = null

    /**
     * [GLSurfaceView.Renderer] that generates and draws the gyrating, cube map textured torus
     * for our demo.
     */
    private inner class Renderer : GLSurfaceView.Renderer {
        /**
         * Flag that indicates (if *true*) that our context supports the GL_OES_texture_cube_map
         * according to the string describing the current connection for the GL10.GL_EXTENSIONS
         * token.
         */
        private var mContextSupportsCubeMap = false
        /**
         * Topologically rectangular array of vertices describing our torus.
         */
        private var mGrid: Grid? = null
        /**
         * Texture name generated by [GL10.glGenTextures], we use [GL10.glBindTexture] to bind this
         * to GL_TEXTURE_CUBE_MAP
         */
        private var mCubeMapTextureID = 0
        @Suppress("unused")
        private val mUseTexGen = false
        /**
         * Angle we use to rotate our torus both around the vector (0,1,0) and (1,0,0), it is
         * advanced by 1.2f degrees every time [onDrawFrame] is called
         */
        private var mAngle = 0f

        /**
         * Called to draw the current frame. First we call our method [checkGLError] which
         * calls [GL10.glGetError] to get any error conditions and throws a [RuntimeException]
         * if any error other than GL_NO_ERROR is returned. If our [Boolean] flag field
         * [mContextSupportsCubeMap] is *true* we set our clear color to blue, otherwise we set it
         * to red. We proceed then to clear both the color buffer and the depth buffer, enable depth
         * test, select the model view as our current matrix and initialize it with the identity
         * matrix. We define our viewing transformation with the eye at (0,0,-5), the center at
         * (0,0,0), and (0.0, 1.0, 0.0) as the up vector. We rotate our model [mAngle] degrees
         * around the y axis, and by [mAngle] times 0.25 degrees around the x axis. Next we enable
         * the client side capability GL_VERTEX_ARRAY (If enabled, the vertex array is enabled for
         * writing and used during rendering when `glArrayElement`, `glDrawArrays`, `glDrawElements`,
         * `glDrawRangeElements`, `glMultiDrawArrays`, or `glMultiDrawElements` is called). We again
         * call [checkGLError] to catch any errors that may have occurred up to this point.
         *
         * Now if our flag [mContextSupportsCubeMap] is *true*, we set the active texture to
         * GL_TEXTURE0, then call [checkGLError] to catch any error that may have occurred,
         * enable the GL_TEXTURE_CUBE_MAP server-side GL capability, then call [checkGLError] again
         * to catch any error that may have occurred. We bind the texture GL_TEXTURE_CUBE_MAP to our
         * texture ID [mCubeMapTextureID] then call [checkGLError] to catch an error that may have
         * occurred. We cast our [GL10] parameter [gl] to an instance of [GL11ExtensionPack] to
         * initialize our variable `val gl11ep`. Call `gl11ep.glTexGeni` to control the generation
         * of texture coordinates for texture coordinate GL_TEXTURE_GEN_STR to be texture-coordinate
         * generation function GL_TEXTURE_GEN_MODE, with the texture generation parameter
         * GL_REFLECTION_MAP (used to create a realistically reflective surface). And once
         * again we call [checkGLError] to catch any errors that may have occurred. We enable
         * the GL_TEXTURE_GEN_STR server-side GL capability (texture coordinates will be generated),
         * then call [checkGLError] to catch any error that may have occurred. Next we call
         * `glTexEnvx` to set the GL_TEXTURE_ENV environment parameter GL_TEXTURE_ENV_MODE to
         * the value GL_DECAL (a decal overlay effect is used).
         *
         * Whether our context supports the GL_OES_texture_cube_map extension or not, we once again
         * call [checkGLError] to catch any errors, then instruct our [Grid] field [mGrid] to draw
         * itself. Then if [mContextSupportsCubeMap] is *true*, we disable the server side capability
         * GL_TEXTURE_GEN_STR, and call [checkGLError] to catch any errors.
         *
         * Finally we advance [Float] field [mAngle] by 1.2 degrees and return to caller.
         *
         * @param gl the GL interface.
         */
        override fun onDrawFrame(gl: GL10) {
            checkGLError(gl)
            if (mContextSupportsCubeMap) {
                gl.glClearColor(0f, 0f, 1f, 0f)
            } else {
                /**
                 * Current context doesn't support cube maps.
                 * Indicate this by drawing a red background.
                 */
                gl.glClearColor(1f, 0f, 0f, 0f)
            }
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT or GL10.GL_DEPTH_BUFFER_BIT)
            gl.glEnable(GL10.GL_DEPTH_TEST)
            gl.glMatrixMode(GL10.GL_MODELVIEW)
            gl.glLoadIdentity()
            GLU.gluLookAt(gl, 0f, 0f, -5f, 0f, 0f, 0f, 0f, 1.0f, 0.0f)
            gl.glRotatef(mAngle, 0f, 1f, 0f)
            gl.glRotatef(mAngle * 0.25f, 1f, 0f, 0f)
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY)
            checkGLError(gl)
            if (mContextSupportsCubeMap) {
                gl.glActiveTexture(GL10.GL_TEXTURE0)
                checkGLError(gl)
                gl.glEnable(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP)
                checkGLError(gl)
                gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, mCubeMapTextureID)
                checkGLError(gl)
                val gl11ep = gl as GL11ExtensionPack
                gl11ep.glTexGeni(GL11ExtensionPack.GL_TEXTURE_GEN_STR,
                        GL11ExtensionPack.GL_TEXTURE_GEN_MODE,
                        GL11ExtensionPack.GL_REFLECTION_MAP)
                checkGLError(gl)
                gl.glEnable(GL11ExtensionPack.GL_TEXTURE_GEN_STR)
                checkGLError(gl)
                gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_DECAL)
            }
            checkGLError(gl)
            mGrid!!.draw(gl)
            if (mContextSupportsCubeMap) {
                gl.glDisable(GL11ExtensionPack.GL_TEXTURE_GEN_STR)
            }
            checkGLError(gl)
            mAngle += 1.2f
        }

        /**
         * Called when the surface changed size. Called after the surface is created and whenever
         * the OpenGL ES surface size changes. First we call our method [checkGLError] to catch any
         * errors that may have occurred. Then we call the [GL10.glViewport] method of our [GL10]
         * parameter [gl] to set the viewport with the lower left corner at (0,0), [width] as the
         * width of the viewport, and [height] as the height of the viewport. We calculate the
         * aspect ratio [Float] variable `val ratio` to be [width] over [height], set the the
         * current matrix to the projection matrix, load it with the identity matrix, and call
         * the [GL10.glFrustumf] method of [gl] to multiply that matrix by the projection matrix
         * created with the left vertical clipping plane `-ratio`, right vertical clipping plane
         * `+ratio`, the bottom clipping plane of -1, top clipping plane of 1, near clipping plane
         * of 1, and far clipping plane of 10. Finally we call our method [checkGLError] to catch
         * any errors that may have occurred.
         *
         * @param gl     the GL interface.
         * @param width  width of new surface
         * @param height height of new surface
         */
        override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
            checkGLError(gl)
            gl.glViewport(0, 0, width, height)
            val ratio = width.toFloat() / height
            gl.glMatrixMode(GL10.GL_PROJECTION)
            gl.glLoadIdentity()
            gl.glFrustumf(-ratio, ratio, -1f, 1f, 1f, 10f)
            checkGLError(gl)
        }

        /**
         * Called when the surface is created or recreated. Called when the rendering thread starts
         * and whenever the EGL context is lost. The EGL context will typically be lost when the
         * Android device awakes after going to sleep. First we call our method [checkGLError] to
         * catch any errors that may have occurred. Then we check whether the current context
         * supports the cube map extension and set our [Boolean] field [mContextSupportsCubeMap]
         * accordingly. We call our method [generateTorusGrid] to create a [Grid] for our field
         * [mGrid] defining our torus. If the current context supports the cube map extension (i.e.
         * our field [mContextSupportsCubeMap] is *true*) we initialize [Int] array variable
         * `val cubeMapResourceIds` with the resource ID's of the six jpg raw resources we will use
         * for the six sides of the cube: R.raw.skycubemap0, R.raw.skycubemap1, R.raw.skycubemap2,
         * R.raw.skycubemap3, R.raw.skycubemap4, and R.raw.skycubemap5. Then we set our [Int] field
         * [mCubeMapTextureID] to the texture ID returned from our method [generateCubeMap] after it
         * turns these jpg images into a cube map texture. Finally we call our  method [checkGLError]
         * to catch any errors that may have occurred.
         *
         * @param gl     the GL interface.
         * @param config the EGLConfig of the created surface. Can be used
         * to create matching pbuffers.
         */
        override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
            checkGLError(gl)
            /**
             * This test needs to be done each time a context is created,
             * because different contexts may support different extensions.
             */
            mContextSupportsCubeMap = checkIfContextSupportsCubeMap(gl)
            mGrid = generateTorusGrid(gl, 60, 60, 3.0f, 0.75f)
            if (mContextSupportsCubeMap) {
                val cubeMapResourceIds = intArrayOf(
                        R.raw.skycubemap0, R.raw.skycubemap1, R.raw.skycubemap2,
                        R.raw.skycubemap3, R.raw.skycubemap4, R.raw.skycubemap5)
                mCubeMapTextureID = generateCubeMap(gl, cubeMapResourceIds)
            }
            checkGLError(gl)
        }

        /**
         * Configures the cube map texture for use, and loads the 6 jpeg's with the resource IDs in
         * the [Int] array parameter [resourceIds] into the 6 cube map texture images (one for each
         * face of the cube).
         *
         * First we call our method [checkGLError] to catch any errors that may have occurred. Next
         * we allocate an [Int] array for variable `val ids` and fill it with one texture ID generated
         * by `glGenTextures` which we assign to [Int] variable `val cubeMapTextureId`. We bind the
         * texture ID to GL_TEXTURE_CUBE_MAP. We set the parameter GL_TEXTURE_MIN_FILTER (The texture
         * minifying function is used whenever the level-of-detail function used when sampling from
         * the texture determines that the texture should be minified) of GL_TEXTURE_CUBE_MAP to
         * GL_LINEAR (uses the weighted average of the four texture elements that are closest to the
         * specified texture coordinates), and the parameter GL_TEXTURE_MAG_FILTER (The texture
         * magnification function is used whenever the level-of-detail function used when sampling
         * from the texture determines that the texture should be magnified) to GL_LINEAR as well.
         *
         * Then we loop through the six images in [Int] array [resourceIds], open each raw resource
         * using [InputStream] variable `val inputStream`, decode that image into [Bitmap] variable
         * `var bitmap`, specify that each `bitmap` is the face of one of the six sided cube map
         * texture (GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
         * GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
         * and GL_TEXTURE_CUBE_MAP_NEGATIVE_Z for the bitmaps generated from [resourceIds] 0 to 5
         * respectively -- the code relies on the fact that they happen to have sequential ID numbers),
         * and then we recycle the [Bitmap] `bitmap`.
         *
         * Finally we call our method [checkGLError] to catch any errors that may have occurred and
         * return the cube map texture ID `cubeMapTextureId` to the caller.
         *
         * @param gl          the GL interface.
         * @param resourceIds the resource IDs of the six jpeg's to be used for the cube map texture
         * @return texture ID of the cube map texture we create
         */
        private fun generateCubeMap(gl: GL10, resourceIds: IntArray): Int {
            checkGLError(gl)
            val ids = IntArray(1)
            gl.glGenTextures(1, ids, 0)
            val cubeMapTextureId = ids[0]
            gl.glBindTexture(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, cubeMapTextureId)
            gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR.toFloat())
            gl.glTexParameterf(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR.toFloat())
            for (face in 0..5) {
                val inputStream: InputStream = resources.openRawResource(resourceIds[face])
                @Suppress("JoinDeclarationAndAssignment")
                var bitmap: Bitmap
                bitmap = try {
                    BitmapFactory.decodeStream(inputStream)
                } finally {
                    try {
                        inputStream.close()
                    } catch (e: IOException) {
                        Log.e("CubeMap", "Could not decode texture for face $face")
                    }
                }
                GLUtils.texImage2D(GL11ExtensionPack.GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, bitmap, 0)
                bitmap.recycle()
            }
            checkGLError(gl)
            return cubeMapTextureId
        }

        /**
         * Generates a [Grid] describing our torus. First we allocate a new [Grid] for variable
         * `val grid` sized to hold [uSteps] plus one times [vSteps] plus one vertices. Next we loop
         * using [Double] variable `val angleV` to divide the outside radius of the torus into [vSteps]
         * segments, calculating the cos `val cosV` of `angleV` and sin `val sinV` of `angleV`. In
         * an inner loop we loop using [Double] variable `val angleU` to divide the body of the torus
         * into [uSteps] segments, calculating the cos `val cosU` of `angleU` and sin `val sinU` of
         * `angleU`. Using these values we are able to calculate the (x,y,z) location of the vertex,
         * and the normal vector of the vertex (nx,ny,nz) and we call the [Grid.set] method of
         * `grid` to store these in their appropriate (i,j) places in the Grid's vertex buffer.
         *
         * When done loading the vertex buffer of [Grid] `grid` we call the [Grid.createBufferObjects]
         * method of `grid` to load the buffer objects describing our torus into the openGL engine
         * and return `grid` to the caller.
         *
         * @param gl          the GL interface.
         * @param uSteps      number of steps for u dimension (width) 60 in our case
         * @param vSteps      number of steps for v dimension (height) 60 in our case
         * @param majorRadius Outer radius of torus donut 3.0f in our case
         * @param minorRadius Radius of body of torus 0.75f in our case
         * @return [Grid] describing our torus
         */
        @Suppress("SameParameterValue")
        private fun generateTorusGrid(
                gl: GL,
                uSteps: Int,
                vSteps: Int,
                majorRadius: Float,
                minorRadius: Float
        ): Grid {
            val grid = Grid(uSteps + 1, vSteps + 1)
            for (j in 0..vSteps) {
                val angleV = Math.PI * 2 * j / vSteps
                val cosV = cos(angleV).toFloat()
                val sinV = sin(angleV).toFloat()
                for (i in 0..uSteps) {
                    val angleU = Math.PI * 2 * i / uSteps
                    val cosU = cos(angleU).toFloat()
                    val sinU = sin(angleU).toFloat()
                    val d = majorRadius + minorRadius * cosU
                    val x = d * cosV
                    val y = d * -sinV
                    val z = minorRadius * sinU
                    var nx = cosV * cosU
                    var ny = -sinV * cosU
                    var nz = sinU
                    val length = sqrt(nx * nx + ny * ny + (nz * nz).toDouble()).toFloat()
                    nx /= length
                    ny /= length
                    nz /= length
                    grid[i, j, x, y, z, nx, ny] = nz
                }
            }
            grid.createBufferObjects(gl)
            return grid
        }

        /**
         * Convenience function to call [checkIfContextSupportsExtension] to check whether the
         * "GL_OES_texture_cube_map" extension is present in the current context.
         *
         * @param gl GL interface
         * @return *true* if the "GL_OES_texture_cube_map" extension is present in the current context.
         */
        private fun checkIfContextSupportsCubeMap(gl: GL10): Boolean {
            return checkIfContextSupportsExtension(gl, "GL_OES_texture_cube_map")
        }

        /**
         * This is not the fastest way to check for an extension, but fine if we are only checking
         * for a few extensions each time a context is created. We add spaces at the beginning and
         * end of the the string returned by `glGetString` for the GL_EXTENSIONS string
         * (which returns the extension string supported by the implementation). Then we use
         * [String.indexOf] to search within that string for our parameter [String] parameter
         * [extension], and return *true* if it is found, *false* if not.
         *
         * @param gl        GL interface
         * @param extension extension to test for
         * @return *true* if the [extension] is present in the current context.
         */
        @Suppress("SameParameterValue")
        private fun checkIfContextSupportsExtension(gl: GL10, extension: String): Boolean {
            val extensions = " " + gl.glGetString(GL10.GL_EXTENSIONS) + " "
            /**
             * The extensions string is padded with spaces between extensions, but not
             * necessarily at the beginning or end. For simplicity, add spaces at the
             * beginning and end of the extensions string and the extension string.
             * This means we can avoid special-case checks for the first or last
             * extension, as well as avoid special-case checks when an extension name
             * is the same as the first part of another extension name.
             */
            Log.i(TAG, "Supports: $extensions")
            return extensions.indexOf(" $extension ") >= 0
        }
    }

    /**
     * A grid is a topologically rectangular array of vertices. This grid class is customized for
     * the vertex data required for this example. The vertex and index data are held in VBO objects
     * (Vertex buffer objects) because on most GPUs VBO objects are the fastest way of rendering
     * static vertex and index data.
     */
    private class Grid(w: Int, h: Int) {
        /**
         * Buffer object name that we use when we upload our vertex buffer ([ByteBuffer] field
         * [mVertexByteBuffer]).
         */
        private var mVertexBufferObjectId = 0
        /**
         * Buffer object name that we use when we upload our index buffer ([CharBuffer] field
         * [mIndexBuffer]).
         */
        private var mElementBufferObjectId = 0

        /**
         * These buffers are used to hold the vertex and index data while
         * constructing the grid. Once createBufferObjects() is called
         * the buffers are nulled out to save memory.
         */

        /**
         * [ByteBuffer] we use to build our vertex and normal vector data in
         */
        private var mVertexByteBuffer: ByteBuffer?
        /**
         * a view of [ByteBuffer] field [mVertexByteBuffer] as a float buffer
         */
        private var mVertexBuffer: FloatBuffer?
        /**
         * [CharBuffer] we use to build our index data array
         */
        private var mIndexBuffer: CharBuffer?
        /**
         * Width of our [Grid] (vertices in one row)
         */
        private val mW: Int
        /**
         * Height of our [Grid] (number of rows of vertices)
         */
        private val mH: Int
        /**
         * Number of entries in our index buffer ([CharBuffer] field [mIndexBuffer])
         */
        private val mIndexCount: Int

        /**
         * Stores the vertex coordinate values in the proper places in our [FloatBuffer] field
         * [mVertexBuffer] vertex buffer. After making sure our address arguments [i] and [j] are
         * in range (throwing [IllegalArgumentException] if they are not), we calculate the [Int]
         * variable `val index` that [i] and [j] point to (`mW*j + i`) and position [FloatBuffer]
         * field [mVertexBuffer] to that position (`index*VERTEX_SIZE/FLOAT_SIZE`), and proceed to
         * deposit the rest of our arguments (`x, y, z, nx, ny, nz`) into [mVertexBuffer] one after
         * the other.
         *
         * @param i  column to set
         * @param j  row to set
         * @param x  x coordinate
         * @param y  y coordinate
         * @param z  z coordinate
         * @param nx x coordinate of normal vector
         * @param ny y coordinate of normal vector
         * @param nz z coordinate of normal vector
         */
        operator fun set(i: Int, j: Int, x: Float, y: Float, z: Float, nx: Float, ny: Float, nz: Float) {
            require(!(i < 0 || i >= mW)) { "i" }
            require(!(j < 0 || j >= mH)) { "j" }
            val index = mW * j + i
            mVertexBuffer!!.position(index * VERTEX_SIZE / FLOAT_SIZE)
            mVertexBuffer!!.put(x)
            mVertexBuffer!!.put(y)
            mVertexBuffer!!.put(z)
            mVertexBuffer!!.put(nx)
            mVertexBuffer!!.put(ny)
            mVertexBuffer!!.put(nz)
        }

        /**
         * Transfers our [FloatBuffer] field [mVertexBuffer] vertex buffer to the hardware buffer
         * GL_ARRAY_BUFFER, and our [CharBuffer] field [mIndexBuffer] index buffer to the hardware
         * index buffer GL_ELEMENT_ARRAY_BUFFER.
         *
         * First we call our method [checkGLError] to catch any errors that may have occurred. Next
         * we allocate 2 ints for [Int] array `val vboIds`, cast our [GL] argument [gl] to set [GL11]
         * variable `val gl11`, and generate two buffer object names in `vboIds`, the first we save
         * in our [Int] field [mVertexBufferObjectId], and the second in [mElementBufferObjectId].
         *
         * To upload the vertex data we bind [mVertexBufferObjectId] to GL_ARRAY_BUFFER, position
         * [mVertexByteBuffer] to its beginning, and call `gl11.glBufferData` to create and
         * initialize the GL_ARRAY_BUFFER buffer object's data store from [mVertexByteBuffer] with
         * the usage hint of GL_STATIC_DRAW (The data store contents will be modified once and used
         * many times, and the data store contents are modified by the application, and used as the
         * source for GL drawing and image specification commands).
         *
         * To upload the index data we bind [mElementBufferObjectId] to GL_ELEMENT_ARRAY_BUFFER,
         * position [mIndexBuffer] to its beginning, and call `gl11.glBufferData` to create and
         * initialize the GL_ELEMENT_ARRAY_BUFFER buffer object's data store from [mIndexBuffer]
         * with the usage hint of GL_STATIC_DRAW (The data store contents will be modified once and
         * used many times, and the data store contents are modified by the application, and used as
         * the source for GL drawing and image specification commands).
         *
         * Since we no longer need the in-memory data we set [mVertexBuffer] and [mIndexBuffer]
         * to *null* so they can be garbage collected, and call our method [checkGLError] to catch
         * any errors that may have occurred.
         *
         * @param gl the [GL] interface.
         */
        fun createBufferObjects(gl: GL) {
            checkGLError(gl)
            // Generate a the vertex and element buffer IDs
            val vboIds = IntArray(2)
            val gl11 = gl as GL11
            gl11.glGenBuffers(2, vboIds, 0)
            mVertexBufferObjectId = vboIds[0]
            mElementBufferObjectId = vboIds[1]
            // Upload the vertex data
            gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId)
            mVertexByteBuffer!!.position(0)
            gl11.glBufferData(
                    GL11.GL_ARRAY_BUFFER,
                    mVertexByteBuffer!!.capacity(),
                    mVertexByteBuffer,
                    GL11.GL_STATIC_DRAW
            )
            gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId)
            mIndexBuffer!!.position(0)
            gl11.glBufferData(
                    GL11.GL_ELEMENT_ARRAY_BUFFER,
                    mIndexBuffer!!.capacity() * CHAR_SIZE,
                    mIndexBuffer,
                    GL11.GL_STATIC_DRAW
            )
            // We don't need the in-memory data any more
            mVertexBuffer = null
            mVertexByteBuffer = null
            mIndexBuffer = null
            checkGLError(gl)
        }

        /**
         * Called from [Renderer.onDrawFrame] to draw our torus when it is called to draw the
         * current frame. First we call our method [checkGLError] to catch any errors that may
         * have occurred, and the we cast our [GL10] argument [gl] to set [GL11] variable `val gl11`.
         * We enable the client-side capability GL_VERTEX_ARRAY (the vertex array is enabled for
         * writing and used during rendering when `glArrayElement`, `glDrawArrays`, `glDrawElements`,
         * `glDrawRangeElements`, `glMultiDrawArrays`, or `glMultiDrawElements` is called). Then we
         * bind our buffer object [mVertexBufferObjectId] to the target GL_ARRAY_BUFFER, and then
         * define an array of vertex data with 3 coordinate per vertex, [Float] data type, a stride
         * of VERTEX_SIZE (since the normal vector is interleaved with the vertex coordinates), and
         * the pointer set to the first vertex (0). Next we enable the client-side capability
         * GL_NORMAL_ARRAY (the normal array is enabled for writing and used during rendering when
         * `glArrayElement`, `glDrawArrays`, `glDrawElements`, `glDrawRangeElements`, `glMultiDrawArrays`,
         * or `glMultiDrawElements` is called). Then we define an array of normals, of [Float] type,
         * stride VERTEX_SIZE (since the normals are interleaved with the vertex coordinates), and a
         * starting pointer that advances us past the first vertex coordinate:
         * VERTEX_NORMAL_BUFFER_INDEX_OFFSET * FLOAT_SIZE (3 floats).
         *
         * We bind our buffer object [Int] field [mElementBufferObjectId] to GL_ELEMENT_ARRAY_BUFFER,
         * and call the [GL11.glDrawElements] method of `gl11`  to draw GL_TRIANGLES (the primitive
         * to render), with [mIndexCount] number of elements to be rendered, with type of the values
         * of our indices being GL_UNSIGNED_SHORT, and an initial pointer of 0 to start at the first
         * index value.
         *
         * Having drawn our torus we now disable the client-side capability GL_VERTEX_ARRAY and
         * GL_NORMAL_ARRAY, and unbind the targets GL_ARRAY_BUFFER, and GL_ELEMENT_ARRAY_BUFFER.
         * Finally we call our method [checkGLError] to catch any errors that may have occurred.
         *
         * @param gl the [GL] interface.
         */
        fun draw(gl: GL10) {
            checkGLError(gl)
            val gl11 = gl as GL11
            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY)
            gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, mVertexBufferObjectId)
            gl11.glVertexPointer(3, GL10.GL_FLOAT, VERTEX_SIZE, 0)
            gl.glEnableClientState(GL10.GL_NORMAL_ARRAY)
            gl11.glNormalPointer(GL10.GL_FLOAT, VERTEX_SIZE, VERTEX_NORMAL_BUFFER_INDEX_OFFSET * FLOAT_SIZE)
            gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mElementBufferObjectId)
            gl11.glDrawElements(GL10.GL_TRIANGLES, mIndexCount, GL10.GL_UNSIGNED_SHORT, 0)
            gl.glDisableClientState(GL10.GL_VERTEX_ARRAY)
            gl.glDisableClientState(GL10.GL_NORMAL_ARRAY)
            gl11.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0)
            gl11.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0)
            checkGLError(gl)
        }

        companion object {
            /**
             * Size of vertex data float elements in bytes:
             */
            const val FLOAT_SIZE = 4
            /**
             * Size of index data char elements in bytes:
             */
            const val CHAR_SIZE = 2
            /**
             * Vertex structure:
             *
             * float x, y, z;
             *
             * float nx, ny, nx;
             *
             * 6 floats are used for each vertex, 3 for the (x,y.z) coordinate, and 3 for the normal
             * vector
             */
            const val VERTEX_SIZE = 6 * FLOAT_SIZE
            /**
             * Offset to the normal vector in a vertex data point.
             */
            const val VERTEX_NORMAL_BUFFER_INDEX_OFFSET = 3
        }

        /**
         * Allocates storage to build our `Grid` in and initializes the contents of the index
         * data array `CharBuffer` field `mIndexBuffer` (a triangle list mesh). First we check to
         * make sure our arguments are within the limitations imposed on us by the size of `char`
         * values, and if not we throw `IllegalArgumentException`. We initialize our `Int` field
         * `mW` with our `Int` argument `w`, and our `Int` field `mH` with our `Int` argument `h`.
         * We calculate the total number of vertices to be `val size = w*h`. We allocate a native
         * byte order `ByteBuffer` capable of holding `size` vertices to initialize our `ByteBuffer`
         * field `mVertexByteBuffer`, and set our `FloatBuffer` field `mVertexBuffer` to a view of
         * `mVertexByteBuffer` as a `FloatBuffer`. We calculate the total number of indices we will
         * need to initialize our field `mIndexCount`, and then initialize our `CharBuffer` field
         * `mIndexBuffer` with a native byte order `ByteBuffer` of the correct size, viewed as a
         * `CharBuffer`.
         *
         * Next we loop through all the index values in `mIndexBuffer`, setting them to index
         * values in groups of two triangles, with three indices for each triangle. The indices for
         * a triangle are assigned in counter clockwise order so that the normal points out of the
         * screen. The result is a `w` by `h` two dimensional rectangle divided into equal triangles.
         * The magic occurs when each vertex corresponding to an index value is assigned a three
         * dimensional coordinate by the code in our method `generateTorusGrid`.
         *
         * The six indices for the two triangle group are set from the row `y` and the column
         * `x` as follows:
         *
         * First triangle
         *
         *  * (y*mW + x) top left corner (a)
         *
         *  * ((y+1)*mW + x) bottom left corner (c)
         *
         *  * (y*mW + x + 1) top right corner (b)
         *
         * Second triangle
         *
         *  * (y*mW + x + 1) top right corner (b)
         *
         *  * ((y+1)*mW + x) bottom left corner (c)
         *
         *  * ((y+1)*mW + x + 1) bottom right corner (d)
         *
         * Parameter: w Width of the `Grid` (vertices in one row)
         * Parameter: h Height of the `Grid` (number of rows of vertices)
         */
        init {
            require(!(w < 0 || w >= 65536)) { "w" }
            require(!(h < 0 || h >= 65536)) { "h" }
            require(w * h < 65536) { "w * h >= 65536" }
            mW = w
            mH = h
            val size = w * h
            mVertexByteBuffer = ByteBuffer.allocateDirect(VERTEX_SIZE * size).order(ByteOrder.nativeOrder())
            mVertexBuffer = mVertexByteBuffer!!.asFloatBuffer()
            val quadW = mW - 1
            val quadH = mH - 1
            val quadCount = quadW * quadH
            val indexCount = quadCount * 6
            mIndexCount = indexCount
            /*
             * Initialize triangle list mesh.
             *
             *     [0]-----[  1] ...
             *      |    /   |
             *      |   /    |
             *      |  /     |
             *     [w]-----[w+1] ...
             *      |       |
             *
             */
            mIndexBuffer = ByteBuffer.allocateDirect(CHAR_SIZE * indexCount)
                    .order(ByteOrder.nativeOrder())
                    .asCharBuffer()

            var i = 0
            for (y in 0 until quadH) {
                for (x in 0 until quadW) {
                    val a = (y * mW + x).toChar()
                    val b = (y * mW + x + 1).toChar()
                    val c = ((y + 1) * mW + x).toChar()
                    val d = ((y + 1) * mW + x + 1).toChar()
                    mIndexBuffer!!.put(i++, a)
                    mIndexBuffer!!.put(i++, c)
                    mIndexBuffer!!.put(i++, b)
                    mIndexBuffer!!.put(i++, b)
                    mIndexBuffer!!.put(i++, c)
                    mIndexBuffer!!.put(i++, d)
                }
            }

        }
    }

    /**
     * Called when the activity is starting. First we call through to our super's implementation of
     * `onCreate`. Then we initialize our [GLSurfaceView] field [mGLSurfaceView] with a new instance
     * of [GLSurfaceView], set its renderer to a new instance of [Renderer], and finally set our
     * content view to [mGLSurfaceView].
     *
     * @param savedInstanceState We do not override [onSaveInstanceState] so do not use.
     */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        /**
         * Create our surface view and set it as the content of our Activity
         */
        mGLSurfaceView = GLSurfaceView(this)
        mGLSurfaceView!!.setRenderer(Renderer())
        setContentView(mGLSurfaceView)
    }

    /**
     * Called after [onRestoreInstanceState], [onRestart], or [onPause], for our activity to start
     * interacting with the user. First we call through to our super's implementation of `onResume`,
     * then we call the `onResume` method of our [GLSurfaceView] field [mGLSurfaceView].
     */
    override fun onResume() {
        /**
         * Ideally a game should implement onResume() and onPause()
         * to take appropriate action when the activity looses focus
         */
        super.onResume()
        mGLSurfaceView!!.onResume()
    }

    /**
     * Called as part of the activity lifecycle when an activity is going into the background, but
     * has not (yet) been killed.  The counterpart to [onResume]. First we call through to our
     * super's implementation of `onPause`, then we call the `onPause` method of our field
     * [mGLSurfaceView].
     */
    override fun onPause() {
        /**
         * Ideally a game should implement onResume() and onPause()
         * to take appropriate action when the activity looses focus
         */
        super.onPause()
        mGLSurfaceView!!.onPause()
    }

    companion object {
        /**
         * TAG used for logging.
         */
        private const val TAG = "CubeMapAct..."

        /**
         * Calls `glGetError` to get the value of the error flag, and if it is not GL_NO_ERROR (0),
         * throws a [RuntimeException].
         *
         * @param gl the GL interface.
         */
        fun checkGLError(gl: GL) {
            val error = (gl as GL10).glGetError()
            if (error != GL10.GL_NO_ERROR) {
                throw RuntimeException("GLError 0x" + Integer.toHexString(error))
            }
        }
    }
}